package com.huawei.admins.platform.gateway.core.server.gateway;

import com.huawei.admins.platform.gateway.core.component.router.RouteFilterHandlerAdapter;
import com.huawei.admins.platform.gateway.core.component.router.RoutePredicateHandlerMapping;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpStatus;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import reactor.core.publisher.Mono;

public class GatewayWebHandler implements WebHandler, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private RoutePredicateHandlerMapping handlerMapping;

    private RouteFilterHandlerAdapter handlerAdapter;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.handlerMapping = applicationContext.getBean("routeHandlerMapping", RoutePredicateHandlerMapping.class);
        this.handlerAdapter = applicationContext.getBean("routeHandlerAdapter", RouteFilterHandlerAdapter.class);
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (this.handlerMapping == null) {
            return createNotFoundError();
        }
        if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
            return handlePreFlight(exchange);
        }

        return Mono.just(this.handlerMapping)
                .flatMap(x -> x.getHandler(exchange))
                .switchIfEmpty(createNotFoundError())
                .flatMap(handler -> handleRequestWith(exchange, handler));
    }

    private Mono<Void> handleRequestWith(ServerWebExchange exchange, Object handler) {
        if (exchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN) {
            return Mono.empty();
        }
        if (handlerAdapter.supports(handler)) {
            return handlerAdapter.handle(exchange, handler).then();
        }
        return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
    }

    public Mono<Void> handlePreFlight(ServerWebExchange exchange) {
        return Mono.just(this.handlerMapping)
                .map(mapping -> mapping.getHandler(exchange))
                .switchIfEmpty(Mono.fromRunnable(() -> exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
                .then();
    }

    private <R> Mono<R> createNotFoundError() {
        return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}
